package com.hirsi.module.auth.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.jwt.JWTUtil;
import com.hirsi.core.config.DefaultKaptchaConfig;
import com.hirsi.core.constant.HttpContextHolder;
import com.hirsi.core.constant.RedisConstant;
import com.hirsi.module.auth.service.AuthService;
import com.hirsi.module.auth.vo.param.AccountLoginParam;
import com.hirsi.module.auth.vo.result.CaptchaResult;
import com.hirsi.module.auth.vo.result.TokenResult;
import com.hirsi.module.manager.entity.Manager;
import com.hirsi.module.manager.service.ManagerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class AuthServiceImpl implements AuthService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private DefaultKaptchaConfig defaultKaptcha;

    @Autowired
    private ManagerService managerService;

    @Value("${hirsi.jwt_key}")
    private String jwtKey;

    /**
     * 获取图片验证码
     * @return
     */
    @Override
    public CaptchaResult captcha() {
        String userAgent = HttpContextHolder.getUserAgent();
        String ipAddr = HttpContextHolder.getIpAddr();
        String tag = SecureUtil.md5(String.format("%s_%s", userAgent, ipAddr));
        String text = defaultKaptcha.createText();
        BufferedImage image = defaultKaptcha.createImage(text);
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        String base64Img = "";
        try {
            ImageIO.write(image, "PNG", bos);
            byte[] bytes = bos.toByteArray();
            Base64.Encoder encoder = Base64.getEncoder();
            base64Img = encoder.encodeToString(bytes);
        } catch (IOException e) {
            throw new RuntimeException("获取验证码图片失败", e);
        }
        redisTemplate.opsForValue().set(String.format(RedisConstant.CAPTCHA_KEY_PREFIX, tag), text, RedisConstant.CAPTCHA_EXPIRE_TIME, TimeUnit.SECONDS);
        return new CaptchaResult(tag, String.format("data:image/png;base64,%s", base64Img), RedisConstant.CAPTCHA_EXPIRE_TIME);
    }

    /**
     * 账号密码登录
     * @param param
     * @return
     */
    @Override
    public TokenResult accountLogin(AccountLoginParam param) {
        Manager manager = managerService.lambdaQuery()
                .and(e -> e.ge(Manager::getUsername, param.getAccount())
                        .or().eq(Manager::getPhone, param.getAccount())
                        .or().eq(Manager::getEmail, param.getAccount()))
                .select(Manager::getId, Manager::getPassword).last("limit 1").one();
        if (manager == null) throw new RuntimeException("用户不存在");
        if (!manager.getPassword().equals(param.getPassword())) throw new RuntimeException("密码错误");
        String captcha = redisTemplate.opsForValue().get(String.format(RedisConstant.CAPTCHA_KEY_PREFIX, param.getTag()));
        if (StrUtil.isBlank(captcha)) throw new RuntimeException("图形验证码已过期");
        if (!captcha.equals(param.getCaptcha())) throw new RuntimeException("图形验证码不正确");
        return createToken(manager.getId(), param.getRemember());
    }

    /**
     * 创建令牌
     * @param id
     * @param remember
     * @return
     */
    private TokenResult createToken(Integer id, Boolean remember) {
        JSONObject payload = new JSONObject().set("id", id)
                .set("uuid", UUID.randomUUID().toString());
        String accessToken = JWTUtil.createToken(payload, jwtKey.getBytes());
        long accessExpire = RedisConstant.ACCESS_TOKEN_EXPIRE_TIME;
        redisTemplate.opsForValue().set(String.format(RedisConstant.ACCESS_TOKEN_KEY_PREFIX, id), accessToken, accessExpire, TimeUnit.SECONDS);
        TokenResult result = new TokenResult().setUserId(id);
        result.setAccessToken(accessToken).setAccessExpire(System.currentTimeMillis() - 5000 + accessExpire * 1000);
        if (Boolean.TRUE.equals(remember)) {
            payload.set("accessToken", accessToken);
            String refreshToken = JWTUtil.createToken(payload, jwtKey.getBytes());
            long refreshExpire = RedisConstant.REFRESH_TOKEN_EXPIRE_TIME;
            redisTemplate.opsForValue().set(String.format(RedisConstant.REFRESH_TOKEN_KEY_PREFIX, id), refreshToken, refreshExpire, TimeUnit.SECONDS);
            result.setRefreshToken(refreshToken).setAccessExpire(System.currentTimeMillis() - 5000 + refreshExpire * 1000);
        }
        return result;
    }
}
